/*
	Copyright 2007-2010 Petr Ohlidal

	This file is a part of GrobMaker

	GrobMaker is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	GrobMaker is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with GrobMaker.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Global.hpp"
#include "Help.hpp"
#include "InputData.hpp"
#include "OutputData.hpp"
#include "Util/StringUtils.hpp"
#include "Util/FileList.hpp"
#include "Util/ArgParser.hpp"
#include "Pegs/PegInputData.hpp"
#include "Pegs/Peg.hpp"
#include "Images/Image.hpp"
#include "LuaGrobfileWriter.hpp"
#include <list>
#include <iterator>
#include <string>
#include <sstream>
#include <iostream>
#include <fstream>
#include <new>
#include <cmath>
#include <vector>
#include <iomanip>
#include <cstdlib>

namespace GrobMaker{

const Pixel yellow(255,255,0,255);
const Pixel green(0,255,0,255);
const Pixel blue(0,0,255,255);
const Pixel aqua(0,255,255,255);

void printVersion(){
	std::cout<<"GROB Maker\nVersion "<<VERSION_MAJOR<<'.'<<VERSION_MINOR;
	if(VERSION_BUGFIX>0){
		std::cout<<'.'<<VERSION_BUGFIX;
	}
	std::cout<<'\n'<<COPYRIGHT<<std::endl;
}

} // namespace GrobMaker

int main( int argc, char* argv[] ){
	using namespace GrobMaker;
	bool verbose=false;
	bool justSearch=false;
	std::ofstream logFile;
	std::streambuf* coutBuf = NULL;
	// Handle arguments
	try{
		InputData inputData;
		std::list<std::string> argWarnings;
		ArgParser args(argc,argv);
		while(args.hasNext()){
			// Params without values
			if(args.checkLongParam("help")){
				printVersion();
				std::cout<<"\n"<<HELP<<std::endl;
				return 0;
			}else if(args.checkLongParam("version")){
				printVersion();
				return 0;
			}else if( (args.checkLongParam("parse-shadow") || args.checkShortParam('e')) ){
				// Only enables shadow parsing. Takes regular sprites as shadow-sprites or searches.
				inputData.shadowParse=true;
				args.goNext();
			}else if( args.checkLongParam("verbose") || args.checkShortParam('v') ){
				verbose=true;
				args.goNext();
			}else if( args.checkLongParam("just-search") || args.checkShortParam('j') ){
				justSearch=true;
				args.goNext();
			}else if( args.checkLongParam("omit-pegs") || args.checkShortParam('i') ){
				inputData.omitPegs=true;
				args.goNext();
			}else if( args.checkLongParam("omit-shades") || args.checkShortParam('I') ){
				inputData.omitShades=true;
				args.goNext();
			// Params with values
			}else if( (args.checkLongParam("add-peg") || args.checkShortParam('P')) && args.hasNext() ){
				args.goNext();
				inputData.addPeg( PegInputData( fs::path(args.getAndGoNext()), true) );
				if( inputData.pegNamesStack.size()>0 ){
					inputData.pegs.back().setPegName(inputData.pegNamesStack.front());
					inputData.pegNamesStack.pop_front();
				}
			}else if( (args.checkLongParam("add-single-peg") || args.checkShortParam('p')) && args.hasNext() ){
				inputData.addPeg( PegInputData( fs::path(args.getNextAndSkip()), false)  );
				if( inputData.pegNamesStack.size()>0 ){
					inputData.pegs.back().setPegName(inputData.pegNamesStack.front());
					inputData.pegNamesStack.pop_front();
				}
			}else if( (args.checkLongParam("single-shade") || args.checkShortParam('h')) && args.hasNext() ){
				inputData.singleShade=true;
				inputData.shadePath = fs::path( args.getNextAndSkip() );
			}else if( (args.checkLongParam("shades-dir") || args.checkShortParam('H')) && args.hasNext() ){
				inputData.singleShade=false;
				inputData.shadePath = fs::path( args.getNextAndSkip() );
			}else if( (args.checkLongParam("single-sprite") || args.checkShortParam('s')) && args.hasNext() ){
				inputData.singleSprite=true;
				inputData.spritePath = fs::path( args.getNextAndSkip() );
			}else if( (args.checkLongParam("sprites-dir") || args.checkShortParam('S')) && args.hasNext() ){
				inputData.singleSprite=false;
				inputData.spritePath = fs::path( args.getNextAndSkip() );
			}else if( (args.checkLongParam("single-pivot") || args.checkShortParam('t')) && args.hasNext() ){
				inputData.pivotPath = fs::path( args.getNextAndSkip() );
				inputData.singlePivot=true;
			}else if( (args.checkLongParam("pivots-dir") || args.checkShortParam('T')) && args.hasNext() ){
				inputData.pivotPath = fs::path( args.getNextAndSkip() );
				inputData.singlePivot=false;
			}else if( (args.checkLongParam("output-dir") || args.checkShortParam('o')) && args.hasNext() ){
				inputData.grobDir = fs::path( args.getNextAndSkip() );
			}else if( (args.checkLongParam("source-dir") || args.checkShortParam('r')) && args.hasNext() ){
				inputData.sourceDir = fs::path(args.getNextAndSkip());
			}else if( (args.checkLongParam("script-path") || args.checkShortParam('c')) && args.hasNext() ){
				inputData.scriptPath = std::string(args.getNextAndSkip());
			}else if( (args.checkLongParam("grob-name") || args.checkShortParam('n')) && args.hasNext() ){
				inputData.grobName = std::string(args.getNextAndSkip());
			}else if( (args.checkLongParam("sprite-width") || args.checkShortParam('w')) && args.hasNext() ){
				args.goNext();
				inputData.spriteTargetW = args.getAsInteger();
				args.goNext();
			}else if( (args.checkLongParam("sprite-height") || args.checkShortParam('h')) && args.hasNext() ){
				args.goNext();
				inputData.spriteTargetH = args.getAsInteger();
				args.goNext();
			}else if( (args.checkLongParam("shadows-dir") || args.checkShortParam('D')) && args.hasNext() ){
				// Enables shadow parsing and sets shadow-sprites dir.
				inputData.shadowParse=true;
				inputData.spritePath = fs::path( args.getNextAndSkip() );
				inputData.singleSprite=false;
			}else if( (args.checkLongParam("single-shadow") || args.checkShortParam('d')) && args.hasNext() ){
				// Enables shadow parsing and sets shadow-sprite location.
				inputData.shadowParse=true;
				inputData.spritePath = fs::path( args.getNextAndSkip() );
				inputData.singleSprite=true;

			}else if( (args.checkLongParam("shadow-alpha") || args.checkShortParam('l')) && args.hasNext() ){
				// Enables shadow parsing and sets the max alpha.
				inputData.shadowParse=true;
				inputData.shadowAlpha = static_cast<float>(std::atof(args.getNextAndSkip()));
			}else if( (args.checkLongParam("set-peg-name") || args.checkShortParam('m')) && args.hasNext() ){
				if( inputData.pegs.size()>0 ){
					inputData.pegs.back().setPegName(std::string(args.getNextAndSkip()));
				}
				else{
					inputData.pegNamesStack.push_back( std::string(args.getNextAndSkip()));
				}
			}else if( args.checkLongParam("log") && args.hasNext() ){
				const char* logPath = args.getNextAndSkip();
				logFile.open(logPath);
				if(!logFile.is_open()){
					std::stringstream err;
					err<<"Could not open log file '"<<logPath<<"'";
					throw Exception(err);
				}
				coutBuf = std::cout.rdbuf(logFile.rdbuf());
				verbose=true;
			}else if( (args.checkLongParam("format") || args.checkShortParam('f')) && args.hasNext() ){
				FREE_IMAGE_FORMAT format;
				std::string ext(args.getNextAndSkip());
				if( Image::resolveExtension( ext, &format ) ){
					inputData.imgOutputExtension = ext;
					inputData.imgOutputFormat = format;
					if(format==FIF_PNG){
						inputData.imgOutputFlags = SAVE_FLAGS_PNG;
					}
				}
				else{
					std::stringstream msg;
					msg<<"Unrecognized image extension: \""<<ext<<"\"";
					argWarnings.push_back(msg.str());
				}
			}else if( (args.checkLongParam("no-visual-angles") || args.checkShortParam('a')) && args.hasNext() ){
				int n = strtol(args.getNextAndSkip(),NULL,0);
				if(n<0){
					throw Exception("Negative number of angles is invalid");
				}
				if(360%n!=0){
					throw Exception("Incorrect number of angles.\
						The number must divide 360 without remainder.");
				}
				else{
					inputData.noAngles=n;
				}
			}else if( args.checkLongParam("out-dir-exists") && args.hasNext() ){
				std::string v(args.getNextAndSkip());
				if( v.compare("exit")==0 ){
					inputData.grobdirExistsAction = EXIT;
				}
				else if( v.compare( "new-name")==0 ){
					inputData.grobdirExistsAction = NEW_NAME;
				}
				else if( v.compare( "continue")==0 ){
					inputData.grobdirExistsAction = CONTINUE;
				}
				else if( v.compare( "ask-action")==0 ){
					inputData.grobdirExistsAction = ASK_ACTION;
				}
				else if( v.compare( "ask-new-name")==0 ){
					inputData.grobdirExistsAction = ASK_NEW_NAME;
				}
				else{
					std::stringstream msg;
					msg<<"Warning: unrecognized parameter of --exists: \""<<v<<"\"";
					argWarnings.push_back(msg.str());
				}
			//}else if( (args.checkParam("") || args.checkShortParam('")) && args.hasNext() ){
			}else{
				std::stringstream msg;
				msg<<"Warning: unrecongnized argument ["<<args.getArgIndex()<<"] '"<<args.getArg()<<"'"<<std::endl;
				argWarnings.push_back(msg.str());
				args.goNext();
			}
		}
		// Print warnings
		if(argWarnings.size()>0){
			for(std::list<std::string>::iterator it = argWarnings.begin();
				it!=argWarnings.end();
				it++
			){
				std::cout<<*it<<std::endl;
			}
		}
		// If shadow parsing is enabled, omit shades and pegs
		if(inputData.shadowParse){
			inputData.omitShades = true;
			inputData.omitPegs=true;
		}
		// Fix local paths
		if(!inputData.spritePath.empty() && !inputData.sourceDir.empty() && !inputData.spritePath.is_complete()){
			inputData.spritePath = inputData.sourceDir / inputData.spritePath;
		}

		//====== searching for missing input data ===== //

		// List the input dir
		std::list<FileInfo> inputDirFiles;
		if( FileList::listDir(inputData.sourceDir, inputDirFiles)==-1 ){
			std::stringstream ss;
			ss<<"Entered source directory '"<<inputData.sourceDir<<"' cannot be opened"<<std::endl;
			throw Exception(ss);
		}
		if(inputDirFiles.size()==0){
			std::stringstream err;
			err<<"Source directory '"<<inputData.sourceDir<<"' is empty";
			throw Exception(err);
		}

		// == Search sprites (or shadow-sprites) and number of areas == //
		if(inputData.spritePath.empty()){ // If sprites are not specified
			bool spritesFound=false;
			// If shadow parsing is enabled, first search for files with "shadow" in name.
			if(inputData.shadowParse){
				std::list<FileInfo>::iterator it = inputDirFiles.begin();
				while( it!=inputDirFiles.end() && !spritesFound ){
					if( StringUtils::toLowerCase(it->entry.path().filename()).find("shadow")!=std::string::npos ){ // If filename contains "shadow"
						spritesFound = inputData.checkSpritesResource( *it );
					}
					it++;
				}
			}
			if( !spritesFound ){
				// Search for files with "sprite" in name.
				std::list<FileInfo>::iterator it=inputDirFiles.begin();

				while(it!=inputDirFiles.end() && !spritesFound){
					std::string dirName = StringUtils::toLowerCase(it->entry.path().filename());
					if( dirName.find("sprite")!=std::string::npos ){
						spritesFound = inputData.checkSpritesResource( *it );
					}
					it++;
				}
			}
			if(!spritesFound){
				std::stringstream ss;
				ss<<"No sprite resources found in \""<<inputData.sourceDir<<"\"";
				throw Exception(ss);
			}
		}
		else{ // If sprites location was specified
			if( inputData.singleSprite==false ){
				// Fix path
				if( !inputData.spritePath.is_complete() ){
					inputData.spritePath = inputData.sourceDir / inputData.spritePath;
				}
				// Get files
				if( FileList::listDir(inputData.spritePath, inputData.spriteFiles)==0 ){
					std::stringstream ss;
					ss<<"Entered sprites dir '"<<inputData.spritePath<<"' is empty.";
					throw Exception(ss);
				}
				else{
					FileList::removeUnindexed(inputData.spriteFiles);
					FileList::removeNonImageFiles(inputData.spriteFiles);
					// Check sprite count if known.
					if(inputData.noAngles!=InputData::noAnglesUnspecified){
						if(static_cast<int>(inputData.spriteFiles.size())<inputData.noAngles){
							std::stringstream ss;
							ss<<"Sprites dir '"<<inputData.spritePath<<"' contains only "
								<<inputData.spriteFiles.size()<<" indexed images, but entered sprite count is "
								<<inputData.noAngles;
							throw Exception(ss);
						}
					}
					// Get sprite count if unknown
					else{
						if( 360%inputData.spriteFiles.size()!=0 ){
							std::stringstream ss;
							ss<<"Entered sprites dir '"<<inputData.spritePath<<"' contains "
								<<inputData.spriteFiles.size()<<" indexed images, which is not a valid"
								<<" sprite count (it must divide 360 without remainder)";
							throw Exception(ss);
						}
						else{
							inputData.noAngles = inputData.spriteFiles.size();
						}
					}
				}
			}
		}

		// == Search pegs == //
		if( inputData.getPegCount()==0 && !inputData.omitPegs ){
			inputData.pegsAutoDetected=true;
			// Find dirs and files with "peg" in name
			for(std::list<FileInfo>::iterator rootIt=inputDirFiles.begin();
				rootIt!=inputDirFiles.end();
				rootIt++){

				if( StringUtils::toLowerCase(rootIt->entry.path().filename()).find("peg")!=std::string::npos ){
					// For every inputDir subdirectory, check two things:
					// 1-if it's a pegs dir
					// 2-if any subdir is a pegs dir.
					if( fs::is_directory( rootIt->status ) ){
						std::list<FileInfo> dirFiles;
						FileList::listDir(rootIt->entry.path(), dirFiles);
						// 1-Checking if it's a pegs dir
						if( !PegFileList::checkPegsDir( rootIt->entry.path(), dirFiles, inputData) ){
							// 2-Check if subdirs are pegs dirs.
							for( std::list<FileInfo>::iterator pegIt=dirFiles.begin();
								pegIt!=dirFiles.end();
								pegIt++)
							{
								if( fs::is_directory( pegIt->status ) ){
									std::list<FileInfo> subdirFiles;
									if(FileList::listDir(pegIt->entry.path(), subdirFiles)){
										PegFileList::checkPegsDir( pegIt->entry.path(), subdirFiles, inputData );
									}
								}
							}
						}

					}
					else{ /* If the file has extension and is an num-indexed image,
						consider it a single-peg file*/
						if(Image::resolveExtension(rootIt->entry.path().extension())
							&& rootIt->index!=FileInfo::noIndex)
						{
							std::list<FileInfo> l;
							l.push_back(*rootIt);
							inputData.addPegFileList(
								PegFileList( l,	rootIt->index, rootIt->entry.path().filename() ) );
						}
					}
				}
			}
		}


		// == Search shades == //
		if(inputData.shadePath.empty() && !inputData.omitShades ){
			std::list<FileInfo>::iterator it=inputDirFiles.begin();
			bool shadesFound=false;
			while(it!=inputDirFiles.end() && !shadesFound){
				if(StringUtils::toLowerCase(it->entry.path().filename()).find("shade")!=std::string::npos ){
					if( fs::is_directory( it->status ) ){
						std::list<FileInfo> shadeDirFiles;
						FileList::listDir( it->entry.path(), shadeDirFiles);
						if(shadeDirFiles.size()==1
							&& Image::resolveExtension(shadeDirFiles.front().entry.path().extension()))
						{
							inputData.shadePath = shadeDirFiles.front().entry.path();
							inputData.singleShade=true;
							shadesFound=true;
						}
						else{
							FileList::removeNonImageFiles(shadeDirFiles);
							FileList::removeUnindexed(shadeDirFiles);
							if(static_cast<int>(shadeDirFiles.size())>=inputData.noAngles){
								inputData.shadeFiles.swap( shadeDirFiles );
								inputData.singleShade=false;
								inputData.shadePath = it->entry.path();
								shadesFound=true;
							}
						}
					}
					else if(Image::resolveExtension(it->entry.path().extension())){
						shadesFound=true;
						inputData.singleShade=true;
						inputData.shadePath = it->entry.path();
					}
				}
				it++;
			}
		}

		// == Search pivots == //
		if(inputData.pivotPath.empty()){
			std::list<FileInfo>::iterator it=inputDirFiles.begin();
			bool pivotsFound=false;
			while(it!=inputDirFiles.end() && !pivotsFound){
				if(StringUtils::toLowerCase(it->entry.path().filename()).find("pivot")!=std::string::npos ){
					if( fs::is_directory( it->status ) ){
						std::list<FileInfo> pivotDirFiles;
						FileList::listDir(it->entry.path(), pivotDirFiles);
						if( pivotDirFiles.size()==1u
							&& Image::resolveExtension(pivotDirFiles.front().entry.path().extension()) )
						{
							inputData.pivotPath = pivotDirFiles.front().entry.path();
							inputData.singlePivot=true;
							pivotsFound=true;
						}
						else{
							//int size = static_cast<int>(pivotDirFiles.size());
							FileList::removeNonImageFiles(pivotDirFiles);
							FileList::removeUnindexed(pivotDirFiles);
							int size = static_cast<int>(pivotDirFiles.size());
							if(size>=inputData.noAngles){
								inputData.pivotFiles.swap( pivotDirFiles );
								inputData.singlePivot=false;
								inputData.pivotPath = it->entry.path();
								pivotsFound=true;
							}
						}

					}
					else if( Image::resolveExtension( it->entry.path() )){
						pivotsFound=true;
						inputData.singlePivot=true;
						inputData.pivotPath = it->entry.path();
					}
				}
				it++;
			}
		}

		// == Enter defaults == //
		// Check grob name
		if( inputData.grobName.empty() ){
			if( !inputData.grobDir.empty() ){
				inputData.grobName = inputData.grobDir.filename();
			}
			else{
				inputData.grobName = inputData.sourceDir.filename();
			}
		}
		// Make sure the grobDir doesn't exist yet (but don't create it yet)
		{
			if( inputData.grobDir.empty() ){
				inputData.grobDir = inputData.sourceDir / fs::path(DEFAULT_GROBDIR_NAME);
			}
			else if( !inputData.grobDir.is_complete() ){
				inputData.grobDir = inputData.sourceDir / inputData.grobDir;
			}
			fs::path origGrobDir = inputData.grobDir;
			bool grobDirOk = false;
			int grobDirNumber = 1;

			while( !grobDirOk ){
				if( fs::exists( inputData.grobDir ) ){
					switch( inputData.grobdirExistsAction ){
						case CONTINUE:
							grobDirOk=true;
							break;
						case EXIT:{
							std::stringstream ss;
							ss<<"GrobDir \""<<inputData.grobDir.string()<<"\" already exists";
							throw Exception(ss);
						}
						case NEW_NAME:{
							std::stringstream newGrobDir;
							newGrobDir<<origGrobDir.string()<<std::setfill('0')<<std::setw(2)<<grobDirNumber;
							inputData.grobDir = fs::path(newGrobDir.str());
							grobDirNumber++;
							break;
						}
						case ASK_NEW_NAME:{
							if(coutBuf!=NULL){
								std::cout.rdbuf(coutBuf);
							}
							std::cout<<"GrobDir \""<<inputData.grobDir.string()<<"\" already exists. Enter new path:";
							std::string newGrobDir;
							std::cin>>newGrobDir;
							try{
								fs::path newGrobPath(newGrobDir);
								inputData.grobDir = newGrobPath;
							}
							catch(std::exception e){
								std::cerr<<"Entered path is invalid: "<<e.what()<<std::endl;
							}
							if(coutBuf!=NULL){
								std::cout.rdbuf(logFile.rdbuf());
							}
							break;
						}
						case ASK_ACTION:{
							if(coutBuf!=NULL){
								std::cout.rdbuf(coutBuf);
							}
							std::cout<<"GrobDir \""<<inputData.grobDir.string()<<"\" already exists.\n"
								"Please specify action (c = continue, a = ask for new name, n = generate new name, e = exit):";
							std::string input;
							std::cin>>input;
							if(coutBuf!=NULL){
								std::cout.rdbuf(logFile.rdbuf());
							}
							switch( input.at(0) ){
								case 'c':
									grobDirOk=true;
									break;
								case 'a':
									inputData.grobdirExistsAction=ASK_NEW_NAME;
									break;
								case 'n':
									inputData.grobdirExistsAction=NEW_NAME;
									break;
								case 'e':
									return 0;
								default:{}
							}
						}
						default:{}
					}
				}
				else{
					grobDirOk=true;
				}
			}
		}
		// Set default output format
		if( inputData.imgOutputExtension.empty() ){
			inputData.imgOutputExtension=DEFAULT_IMG_EXTENSION;
			inputData.imgOutputFormat = DEFAULT_IMG_TYPE;
			inputData.imgOutputFlags = DEFAULT_IMG_FLAGS;
		}
		// == Process user-entered peg dirs into file lists. ==
		if(inputData.pegs.size()>0){
			int pegIndex = 1;
			for( std::list<PegInputData>::iterator it = inputData.pegs.begin();
				it!=inputData.pegs.end();
				it++ )
			{
				if( it->singlePeg ){
					std::list<FileInfo> l;
					l.push_back( FileInfo( it->path ) );
					inputData.addPegFileList( PegFileList( l, pegIndex, it->getPegName() ) );
					pegIndex++;
				}
				else{
					std::list<FileInfo> files;
					if( FileList::listDir( it->path, files ) >-1 ){
						if( files.size()!= static_cast<unsigned>( inputData.noAngles ) ){
							std::cout<<"Warning: Pegs directory '"<<it->path.string()<<"' contains only "
								<<files.size()<<" files, but NoAngles is "<<inputData.noAngles<<". Ignoring the dir."<<std::endl;
						}
						else{
							PegFileList l( files, pegIndex );
							if( !it->hasPegName() ){
								l.setPegName(it->path.filename());
							}
							else{
								l.setPegName(it->getPegName());
							}
							pegIndex++;
							inputData.addPegFileList( l );
						}
					}
					else{

						std::cout<<"Error reading pegs dir '"<<it->path.string()<<"'"<<std::endl;
					}
				}
			}
		}
		// == Check peg indexes == //
		// First, find the highest index
		int highestIndex = 0;
		for( std::list<PegFileList>::iterator it=inputData.pegFileLists.begin();
			it!=inputData.pegFileLists.end();
			it++ )
		{
			if( it->pegIndex > highestIndex ){
				highestIndex = it->pegIndex;
			}
		}
		// Assign missing peg indexes
		for( std::list<PegFileList>::iterator it=inputData.pegFileLists.begin();
			it!=inputData.pegFileLists.end();
			it++ )
		{
			if( it->pegIndex <0 ){
				it->pegIndex = ++highestIndex;
			}
		}

		// Check single sprite path
		if( inputData.singleSprite && !inputData.spritePath.is_complete() ){
			inputData.spritePath = inputData.sourceDir / inputData.spritePath;
		}

		// ================== Printing input data =================== //
		if(verbose || justSearch){
			std::cout<<"* Source dir:\n"<<inputData.sourceDir<<"\n";
			std::cout<<"* Sprites"<<(inputData.shadowParse? " (shadow)":"")<<":\n"
				<<((inputData.singleSprite)?"[Single] ":"[Dir] ")<<inputData.spritePath.string()<<"\n";
			// Pegs
			if(!inputData.omitPegs){
				std::cout<<"* Pegs:\n";
				if(inputData.pegFileLists.size()>0){
					for( std::list<PegFileList>::iterator pit = inputData.pegFileLists.begin();
					pit!=inputData.pegFileLists.end();
					pit++)
					{
						if(pit->files.size()==1){
							std::cout<<"[Single] "<<pit->files.front().entry.path().string();
						}
						else{
							std::cout<<"[Dir] "<<pit->files.front().entry.path().parent_path().string();
						}
						// Print name
						if(pit->hasPegName()){
							std::cout<<" Name:"<<pit->getPegName()<<'\n';
						}else{
							std::cout<<'\n';
						}
					}
				}
				else{
					std::cout<<"[None]\n";
				}
			}
			// Shades
			if( !inputData.omitShades ){
				std::cout<<"* Shades:\n";
				if(inputData.shadeFiles.size()>0){
					std::cout<<((inputData.singleShade)?"[Single] ":"[Dir] ")
					<<inputData.shadePath.string()<<"\n";
				}
				else{
					std::cout<<"[None]\n";
				}
			}
			// Pivots
			std::cout<<"* Pivots:\n"<<((inputData.singlePivot)?"[Single] ":"[Dir] ")
				<<inputData.pivotPath.string()<<"\n";
			// Grob dir
			std::cout<<"* Grob output dir:\n"<<inputData.grobDir<<std::endl;
		}
		if(justSearch){
			// Done
			FreeImage_DeInitialise();
			return 0;
		}

		// == Create grob dir == //
		fs::create_directories( inputData.grobDir );

		// ======================= Processing ======================== //
		OutputData outputData;
		FreeImage_Initialise();
		AreaFinder pegFloorFinder( aqua, aqua, Pixel(1,1,1,0) );
		AreaFinder nonzeroAlphaFinder( Pixel(0,0,0,1), Pixel(0,0,0,255), Pixel(0,0,0,1) );
		AreaFinder pivotFinder( yellow, yellow, Pixel(1,1,1,0) );
		AreaFinder shadeFinder( green, green, Pixel(1,1,1,0) );
		AreaFinder pegFinder( blue, blue, Pixel(1,1,1,0) );
		AreaFinder shadowFinder( Pixel(0,0,0,0), Pixel(254,254,254,0), Pixel(1,1,1,0) );

		// ===== Processing Sprite(s) ===== //
		inputData.spriteFiles.sort(FileList::sortFileInfoByIndex);

		if(verbose){
			std::cout<<"---- Sprites ----"<<std::endl;
		}
		if(inputData.singleSprite){
			Image sprite( inputData.spritePath );
			sprite.scale( inputData.spriteTargetW, inputData.spriteTargetH );
			outputData.spritePicSize.w = sprite.getWidth();
			outputData.spritePicSize.h = sprite.getHeight();
			Rectangle trimRect;
			if( !sprite.findAreaRect( trimRect, (inputData.shadowParse ? shadowFinder : nonzeroAlphaFinder) ) ){
				std::stringstream err;
				err<<"No graphic found in single sprite '"<<sprite.getPath()<<"'";
				throw Exception(err);
			}
			outputData.imageAreas.push_back(trimRect);
			std::cout<<trimRect<<std::endl;
			sprite.trim(trimRect);
			std::stringstream outFilename;
			outFilename<<"Sprite."<<inputData.imgOutputExtension;
			fs::path savePath = inputData.grobDir / fs::path(outFilename.str());
			if(verbose){
				std::cout<<"[Single]:\nSRC:"<<sprite.getPath()<<"\nDST:"<<savePath<<std::endl;
			}
			if( inputData.shadowParse ){
				Image::parseShadowSprite( sprite, inputData.shadowAlpha );
			}
			sprite.save(savePath, inputData.imgOutputFormat, inputData.imgOutputFlags);
		}
		else{
			int spriteIndex=0;
			bool sizeSaved=false;
			inputData.spriteFiles.sort(FileList::sortFileInfoByIndex);
			for(std::list<FileInfo>::iterator it = inputData.spriteFiles.begin();
				it!=inputData.spriteFiles.end();
				it++)
				{
				Image sprite(it->entry.path());

				// Scale
				sprite.scale( inputData.spriteTargetW, inputData.spriteTargetH );
				// Save size
				if(!sizeSaved){
					outputData.spritePicSize.w = sprite.getWidth();
					outputData.spritePicSize.h = sprite.getHeight();
					sizeSaved=true;
				}
				// Check if the sprite image has correct size
				if( outputData.spritePicSize.w != static_cast<int>( sprite.getWidth() )
					|| outputData.spritePicSize.h != static_cast<int>(  sprite.getHeight() ) )
					{
					std::stringstream err;
					err<<"Sprite #"<<spriteIndex<<" has diferent scale ("
						<<sprite.getWidth()<<"x"<<sprite.getHeight()<<") than others ("
						<<outputData.spritePicSize.w<<"x"<<outputData.spritePicSize.h<<")";
					throw Exception(err);
				}
				// Find the borders of the graphic.
				Rectangle trimRect;
				if( !sprite.findAreaRect( trimRect, (inputData.shadowParse ? shadowFinder : nonzeroAlphaFinder) )  ){
					std::stringstream err;
					err<<"No graphic found in sprite '"<<sprite.getPath().string()<<"'";
					throw Exception(err);
				}
				outputData.imageAreas.push_back(trimRect);
				// Cut off empty edges
				sprite.trim(trimRect);
				// Save the file
				std::stringstream outFilename;
				outFilename<<std::setfill('0')<<std::setw(3)<<spriteIndex<<'.'<<inputData.imgOutputExtension;
				fs::path savePath = inputData.grobDir / fs::path(outFilename.str());
				if(verbose){
					std::cout<<"["<<spriteIndex<<"]:\n"
						<<"SRC: "<<sprite.getPath()<<" (index:"<<it->index<<")"<<"\nX:"<<trimRect.x<<" Y:"<<trimRect.y
						<<" W:"<<trimRect.w<<" H:"<<trimRect.h<<"\nDST:"<<savePath<<std::endl;
				}
				//sprite.save(dbgPath , inputData.imgOutputFormat, inputData.imgOutputFlags);
				if( inputData.shadowParse ){
					Image::parseShadowSprite( sprite, inputData.shadowAlpha );
				}
				sprite.save(savePath, inputData.imgOutputFormat, inputData.imgOutputFlags);
				spriteIndex++;
			}
		}
		// ===== Process pivot(s) ===== //
		if(verbose){
			std::cout<<"---- Pivots ----"<<std::endl;
		}
		if(inputData.singlePivot){
			// Load image
			Image pivot(inputData.pivotPath);
			// Get the center, scale it, save it
			Area yelArea;
			pivot.findArea( yelArea, pivotFinder );
			Vector pivotPoint = yelArea.getCenter().scale(
				pivot.getWidth(), pivot.getHeight(),
				outputData.spritePicSize.w,outputData.spritePicSize.h);
			if(verbose){
				std::cout<<"[Single]:\nX:"<<pivotPoint.x
					<<" Y:"<<pivotPoint.y<<" SRC:"<<pivot.getPath()<<std::endl;
			}
			outputData.pivots.push_back(pivotPoint);
		}
		else{
			int angleIndex=0;
			inputData.pivotFiles.sort(FileList::sortFileInfoByIndex);
			for(std::list<FileInfo>::iterator it=inputData.pivotFiles.begin();
				it!=inputData.pivotFiles.end();
				it++)
				{
				// Load image
				Image pivot(it->entry.path());
				// Get the center, scale it, save it
				Area yelArea;
				pivot.findArea( yelArea, pivotFinder );
				Vector pivotVec = yelArea.getCenter().scale(
					pivot.getWidth(), pivot.getHeight(),
					outputData.spritePicSize.w,outputData.spritePicSize.h);
				if(verbose){
					std::cout<<"["<<angleIndex<<"]\nSRC: "<<pivot.getPath()<<" (index:"<<it->index<<")"<<"\nX:"<<pivotVec.x<<" Y:"<<pivotVec.y<<"\n";
				}
				outputData.pivots.push_back(pivotVec);
				angleIndex++;
			}
		}

		// ===== Process shade(s) ===== //
		if(!inputData.omitShades){
			if(verbose){
				std::cout<<"---- Shades ----"<<std::endl;
			}
			if(inputData.shadeFiles.size()>0){
				if(inputData.singleShade){
					Image shadePic(inputData.shadePath);
					Area shadeArea;
					shadePic.findArea( shadeArea, shadeFinder );
					if(verbose){
						std::cout<<"[Single]\n"<<inputData.shadePath
							<<"\nX:"<<shadeArea.x<<" Y:"<<shadeArea.y
							<<" W:"<<shadeArea.w<<" H:"<<shadeArea.h<<std::cout;
					}
					outputData.shades.push_back(
						shadeArea.scale(
							shadePic.getWidth(),shadePic.getHeight(),
							outputData.spritePicSize.w, outputData.spritePicSize.h));
				}
				else{
					int angleIndex=0;
					for(std::list<FileInfo>::iterator it=inputData.shadeFiles.begin();
						it!=inputData.shadeFiles.end();
						it++)
						{
						// Load image
						Image shadePic( it->entry.path() );
						// Get shade, scale it, save it
						Area shadeArea;
						shadePic.findArea(shadeArea, shadeFinder);
						if(verbose){
							std::cout<<"["<<angleIndex<<"]\n"<<inputData.shadePath
								<<"\nX:"<<shadeArea.x<<" Y:"<<shadeArea.y
								<<" W:"<<shadeArea.w<<" H:"<<shadeArea.h<<std::endl;
						}
						outputData.shades.push_back(
							shadeArea.scale(
								shadePic.getWidth(),shadePic.getHeight(),
								outputData.spritePicSize.w, outputData.spritePicSize.h));
						angleIndex++;
					}
				}
			}
		}

		// ===== Process pegs ===== //

		// Process the file lists
		if(inputData.pegFileLists.size()>0 && !inputData.omitPegs ){
			if(verbose){
				std::cout<<"---- Pegs ----"<<std::endl;
			}
			// Sort the peg file lists by peg-index
			inputData.pegFileLists.sort(PegFileList::sortPegFileListsByPegIndex);
			// Loop file lists and process pegs
			int pegIndex=0;
			for(std::list<PegFileList>::iterator it=inputData.pegFileLists.begin();
				it!=inputData.pegFileLists.end();
				it++){
				// Single peg
				std::list<PegPosition> pegRing;
				if(it->files.size()==1){
					if(verbose){
						std::cout<<"["<<pegIndex<<", Single]\n"<<it->files.front().entry.path().string()<<std::endl;
					}
					fs::path path = it->files.front().entry.path();
					pegRing.push_back(PegPosition::parsePegPosFromImage(path, outputData, pegFinder, pegFloorFinder));
				}
				// Peg per sprite
				else{
					int angleIndex=0;
					// Sort files by index
					it->files.sort(FileList::sortFileInfoByIndex);
					// Loop files and get pegs.
					for(std::list<FileInfo>::iterator fileIt = it->files.begin();
					fileIt!=it->files.end();
					fileIt++)
					{
						pegRing.push_back(PegPosition::parsePegPosFromImage( fileIt->entry.path(), outputData, pegFinder, pegFloorFinder));
						angleIndex++;
						if(verbose){
							std::cout<<"["<<pegIndex<<", "<<angleIndex<<"]\n"
								<<fileIt->entry.path().string()<<" (index:"<<fileIt->index<<")"<<std::endl;
						}
					}
				}
				outputData.pegs.push_back( Peg( it->getPegName(), pegRing ) );
				pegIndex++;
			}

		}

		// ===== Writing grobfile ===== //
		if(verbose){
			std::cout<<"---- Writing grobfile ----"<<std::endl;
		}
		LuaGrobfileWriter writer(inputData.grobDir.string()+"/grob.lua", outputData, inputData);
		writer.printHeader();
		writer.printSprites();
		writer.printImageAreas();
		writer.printShades();
		writer.printPegs();
	}
	catch(Exception e){
		std::cout<<"---- Error ----\n"<<e.toString()<<std::endl;
		FreeImage_DeInitialise();
		return 1;
	}
	catch( std::exception stde ){
		std::cout<<"---- Error ----\n"<<stde.what()<<std::endl;
		FreeImage_DeInitialise();
		return 2;
	}
	if(logFile.is_open()){
		std::cout.rdbuf(coutBuf);
		logFile.close();
	}
	FreeImage_DeInitialise();
	return 0;
}


